/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab-modules.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

//===========================================================================================
#include <dylab/utils/log/Log.h>
#include <dylab/utils/HashGenerator.h>
#include <dylab/utils/exceptions/EDoesNotExists.h>
#include <dylab/utils/math/Matrix3x3.h>

//===========================================================================================
#include "TetraMesh_ModelStructure.h"

//===========================================================================================
const dylab::String dylab::TetraMesh_ModelStructure::COMPONENT_NAME = "t4mesh";

//===========================================================================================
// Constructors & destructor
//===========================================================================================
dylab::TetraMesh_ModelStructure::TetraMesh_ModelStructure() 
: ComModelStructure(COMPONENT_NAME), MassPointsFeatureBase(this)
, TetrahedralVolumeFeatureBase(this), BoundaryMeshFeatureBase(this)
, mBoundaryMeshTopologyNeedsUpdate(true), mBoundaryMeshGeometryNeedsUpdate(true)
, mNodeDataContainer(false), mEdgeContainer(true), mTriContainer(true), mTetraContainer(true)
, mBoundaryVertexContainer(true), mBoundaryFaceContainer(true)
{
}
//-----------------------------------------------------------------------------
dylab::TetraMesh_ModelStructure::~TetraMesh_ModelStructure() 
{ 
    mNodeArrayList.removeElements(0, mNodeArrayList.getElementCount());
    mHFaceArrayList.removeElements(0, mHFaceArrayList.getElementCount());
    mOppositeHFaceArrayList.removeElements(0, mOppositeHFaceArrayList.getElementCount());
    mNodeDataContainer.removeAll();
    mEdgeContainer.removeAll();
    mTriContainer.removeAll();
    mTetraContainer.removeAll();
    mBoundaryVertexContainer.removeAll();
    mBoundaryFaceContainer.removeAll();
    mNodeVertexMap.removeAll();
}
//-----------------------------------------------------------------------------

//===========================================================================================
// IComponent methods
//===========================================================================================
dylab::String dylab::TetraMesh_ModelStructure::getDescription() const
{
    return String::fmt("%1%(n=%2%,t=%3%)", getFullName(), getNodeCount(), getTetraCount());
}
//-----------------------------------------------------------------------------

//===========================================================================================
// ISimObjectComponent methods
//===========================================================================================
void dylab::TetraMesh_ModelStructure::initialize(SimObject * _parentSimObject)
{
	// check & store pointer to the parent simulation object
	DYLAB_ASSERT(_parentSimObject != NULL);
	mParentSimObject = _parentSimObject;
}
//-----------------------------------------------------------------------------

//===========================================================================================
// MassPointsFeatureBase methods
//===========================================================================================
dylab::MassPoint::id_t dylab::TetraMesh_ModelStructure::createMassPoints(uint32_t count)
{
    MassPoint::id_t first = addNodes(count);

    for (uint32_t i = 0; i < count; i++)
    {
        MassPoint * p =  & mNodeArrayList[first + i];
        mNodeDataContainer.insert(first + i, p);
    }

    // return the new mass point
    return first;
}
//-----------------------------------------------------------------------------
dylab::MassPointContainer::Iterator dylab::TetraMesh_ModelStructure::massPointIterator()
{
    // TODO: better need-for-update check
    //mBoundaryMeshTopologyNeedsUpdate = true;
    mBoundaryMeshGeometryNeedsUpdate = true;

    // return the node data container iterator
    return NodeDataContainer::Iterator(mNodeDataContainer);
}
//-----------------------------------------------------------------------------
dylab::MassPointContainer::ConstIterator dylab::TetraMesh_ModelStructure::massPointIterator() const
{
    // return the node data container iterator
    return NodeDataContainer::ConstIterator(mNodeDataContainer);
}
//-----------------------------------------------------------------------------
dylab::MassPointContainer::Reader dylab::TetraMesh_ModelStructure::massPointReader() const
{
    return NodeDataContainer::Reader(mNodeDataContainer);
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::prepareForPointRendering()
{
    // nothing to do here
}
//-----------------------------------------------------------------------------
dylab::MassPoint::id_t
dylab::TetraMesh_ModelStructure::getNearestMassPointId(const Vector3 & _pos)
{
    bool found = false;
    MassPoint::id_t nearestMassPointId = 0;
    real_t nearestDist = NumericTraits<real_t>::maximum();

    for (NodeDataContainer::Iterator node(mNodeDataContainer); node.isValid(); node++)
    {
        real_t dist = (_pos - node->currState().position).length();
        if (dist < nearestDist)
        {
            found = true;
            nearestDist = dist;
            nearestMassPointId = node->getId();
        }
    }

    if (!found)
        throw EDoesNotExists("There are no mass points!");

    return nearestMassPointId;
}
//-----------------------------------------------------------------------------


//===========================================================================================
// TetrahedralVolumeFeatureBase methods
//===========================================================================================
dylab::Tetrahedron::id_t
dylab::TetraMesh_ModelStructure::createTetrahedron(MassPoint::id_t n1, MassPoint::id_t n2, MassPoint::id_t n3, MassPoint::id_t n4) 
{
    // store tetra's and first half face' ids
    hfaceid_t firstHFaceID = mHFaceArrayList.addElements(4);
    tetraid_t tetraID = firstHFaceID / 4;

    // TODO - check if the apexes are in the right order?
    
    mHFaceArrayList[firstHFaceID + 0] = n1; getNode(n1)->incRefs();
    mHFaceArrayList[firstHFaceID + 1] = n2; getNode(n2)->incRefs();
    mHFaceArrayList[firstHFaceID + 2] = n3; getNode(n3)->incRefs();
    mHFaceArrayList[firstHFaceID + 3] = n4; getNode(n4)->incRefs();

    // insert the created tetrahedron into the container
    mTetraContainer.insert(tetraID, new Tetrahedron(tetraID, massPointReader().get(n1), massPointReader().get(n2), 
                                                             massPointReader().get(n3), massPointReader().get(n4)));

    // update the opposite half-face list also    
    mOppositeHFaceArrayList.addElements(4);
    _addTri(firstHFaceID + 0, n2, n3, n4);
    _addTri(firstHFaceID + 1, n3, n4, n1);
    _addTri(firstHFaceID + 2, n4, n1, n2);
    _addTri(firstHFaceID + 3, n1, n2, n3);

    // update the edge container
    _addEdge(n1, n2);
    _addEdge(n1, n3);
    _addEdge(n1, n4);
    _addEdge(n2, n3);
    _addEdge(n2, n4);
    _addEdge(n3, n4);

    // return id of the created tetrahedron
    return tetraID;
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::destroyTetrahedron(Tetrahedron::id_t _tetraID)
{
    DYLAB_LOGF("%1% can't remove the tetrahedron.", COMPONENT_NAME);
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::subdivideTetrahedron(Tetrahedron::id_t _tetraID, const Vector3 & _point)
{
    DYLAB_LOGF("%1% can't subdivide tetrahedron.", COMPONENT_NAME);
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::subdivideEdge(Edge::id_t _edgeID, const Vector3 & _point)
{
    DYLAB_LOGF("%1% can't subdivide edge.", COMPONENT_NAME);
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::subdivideTriangle(Triangle::id_t _triID, const Vector3 & _point)
{
    DYLAB_LOGF("%1% can't subdivide triangle.", COMPONENT_NAME);
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::collapseEdge(Edge::id_t _edgeID)
{
    DYLAB_LOGF("%1% can't collapse edge.", COMPONENT_NAME);
}
//-----------------------------------------------------------------------------
dylab::EdgeContainer::Iterator 
dylab::TetraMesh_ModelStructure::edgeIterator()
{
    // return edge container iterator
    return EdgeContainer::Iterator(mEdgeContainer);
}
//-----------------------------------------------------------------------------
dylab::EdgeContainer::ConstIterator 
dylab::TetraMesh_ModelStructure::edgeIterator() const
{
    // return edge container iterator
    return EdgeContainer::ConstIterator(mEdgeContainer);
}
//-----------------------------------------------------------------------------
dylab::EdgeContainer::Reader 
dylab::TetraMesh_ModelStructure::edgeReader() const
{
    // return edge container reader
    return EdgeContainer::Reader(mEdgeContainer);
}
//-----------------------------------------------------------------------------
dylab::TriangleContainer::Iterator 
dylab::TetraMesh_ModelStructure::triangleIterator()
{
    // return triangle container iterator
    return TriContainer::Iterator(mTriContainer);
}
//-----------------------------------------------------------------------------
dylab::TriangleContainer::ConstIterator 
dylab::TetraMesh_ModelStructure::triangleIterator() const
{
    // return triangle container iterator
    return TriContainer::ConstIterator(mTriContainer);
}
//-----------------------------------------------------------------------------
dylab::TriangleContainer::Reader 
dylab::TetraMesh_ModelStructure::triangleReader() const
{
    // return triangle container reader
    return TriContainer::Reader(mTriContainer);
}
//-----------------------------------------------------------------------------
dylab::TetrahedronContainer::Iterator 
dylab::TetraMesh_ModelStructure::tetrahedronIterator()
{
    // return tetrahedron container iterator
    return TetraContainer::Iterator(mTetraContainer);
}
//-----------------------------------------------------------------------------
dylab::TetrahedronContainer::ConstIterator 
dylab::TetraMesh_ModelStructure::tetrahedronIterator() const
{
    // return tetrahedron container iterator
    return TetraContainer::ConstIterator(mTetraContainer);
}
//-----------------------------------------------------------------------------
dylab::TetrahedronContainer::Reader 
dylab::TetraMesh_ModelStructure::tetrahedronReader() const
{
    // return tetrahedron container reader
    return TetraContainer::Reader(mTetraContainer);
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::prepareForVolumeRendering()
{
    // nothing to do here
}
//-----------------------------------------------------------------------------


//===========================================================================================
// BoundaryMeshFeatureBase methods
//===========================================================================================
void dylab::TetraMesh_ModelStructure::generateFromMassPoints()
{
	//TODO: not implemented yet
	DYLAB_NOT_IMPLEMENTED();
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::generateFromTetrahedralVolume()
{
    // initialize the boundary vertex & face container, first phase - build the structures
    for (uint32_t i = 0; i < mOppositeHFaceArrayList.getElementCount(); i++) 
    {
        // if the face is boundary
        if (mOppositeHFaceArrayList.getElement(i) == OHF_NONE) 
        {   
            // retrieve face node ids
            hfaceid_t faceID = getHFaceIndexID(i);
            nodeid_t nodeID[TriangleGeometry3::V_COUNT];
            getHFaceNodeIDs(faceID, &nodeID[0], &nodeID[1], &nodeID[2]); 

            // store the nodes into the array
            NodeTraits * node[TriangleGeometry3::V_COUNT];
            for (uint32_t i = 0; i < TriangleGeometry3::V_COUNT; i++)
                node[i] = & mNodeArrayList[nodeID[i]];

            // compute the face normal
            Vector3 faceNormal = (node[1]->currState().position - node[0]->currState().position).cross(node[2]->currState().position - node[0]->currState().position).normalise();

            // declare array for storing vertex indices of the new face
            Vertex * faceVertices[TriangleGeometry3::V_COUNT];

            // for all face nodes
            for (TriangleGeometry3::VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
            {
                Vertex * vertex;
                uint32_t vertexIndex;
                
                // if node is in node-vertex map
                if (mNodeVertexMap.find(nodeID[vertexIdx], &vertexIndex))
                {
                    // retrieve the vertex by the its associated index to the vertex container
                    vertex = mBoundaryVertexContainer[vertexIndex];   
                }
                else
                {
                    // create vertex for that node & set its position and texture coordinates
                    vertex = new _Vertex(nodeID[vertexIdx], mBoundaryVertexContainer.getCount(), 
						node[vertexIdx]->currState().position, Vector3::ZERO, 0, 0);
                    DYLAB_ASSERT(vertex != NULL);
                    
                    // store the created vertex into the vertex container
                    mBoundaryVertexContainer.insertAtEnd(vertex);

                    // store the (node id), (vertex index) pair into the node-vertex map
                    mNodeVertexMap.insert(nodeID[vertexIdx], vertex->getIndex());
                }
                
                // add the face normal to the vertex normal and increment the face-normal counter
                vertex->addFaceNormal(faceNormal);

                // store the appropriate vertex index into the face
                faceVertices[vertexIdx] = vertex;
            } 

            // store the face into the face container
            mBoundaryFaceContainer.insert(faceID, new Face(faceVertices, faceNormal));
        }
    }
    
    // initialize the boundary vertex & face container, second phase - compute vertex normals
    for (BoundaryVertexContainer::Iterator vertex(mBoundaryVertexContainer); vertex.isValid(); vertex++)
        vertex->endNormalCalculation();    // compute vertex normal

    // no need to update them now
    mBoundaryMeshTopologyNeedsUpdate = false;
    mBoundaryMeshGeometryNeedsUpdate = false;
}
//-----------------------------------------------------------------------------
dylab::uint32_t 
dylab::TetraMesh_ModelStructure::createVertex(const Vector3 & _position, const Vector3 & _normal, real_t _u, real_t _v)
{
	DYLAB_NOT_IMPLEMENTED();
	return 0;
}
//-----------------------------------------------------------------------------
dylab::Face::id_t 
dylab::TetraMesh_ModelStructure::createFace(uint32_t  _v1, uint32_t  _v2, uint32_t _v3)
{
	DYLAB_NOT_IMPLEMENTED();
	return 0;
}
//-----------------------------------------------------------------------------
dylab::VertexContainer::Iterator 
dylab::TetraMesh_ModelStructure::vertexIterator()
{
    // return the container iterator
    return BoundaryVertexContainer::Iterator(mBoundaryVertexContainer);
}
//-----------------------------------------------------------------------------
dylab::VertexContainer::ConstIterator 
dylab::TetraMesh_ModelStructure::vertexIterator() const
{
    // return the container iterator
    return BoundaryVertexContainer::ConstIterator(mBoundaryVertexContainer);
}
//-----------------------------------------------------------------------------
dylab::VertexContainer::Reader 
dylab::TetraMesh_ModelStructure::vertexReader() const
{
    // return the container reader
    return BoundaryVertexContainer::Reader(mBoundaryVertexContainer);
}
//-----------------------------------------------------------------------------
dylab::FaceContainer::Iterator 
dylab::TetraMesh_ModelStructure::faceIterator()
{
    // return the container iterator
    return BoundaryFaceContainer::Iterator(mBoundaryFaceContainer);
}
//-----------------------------------------------------------------------------
dylab::FaceContainer::ConstIterator 
dylab::TetraMesh_ModelStructure::faceIterator() const
{
    // return the container iterator
    return BoundaryFaceContainer::ConstIterator(mBoundaryFaceContainer);
}
//-----------------------------------------------------------------------------
dylab::FaceContainer::Reader 
dylab::TetraMesh_ModelStructure::faceReader() const
{
    // return the container reader
    return BoundaryFaceContainer::Reader(mBoundaryFaceContainer);
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::prepareForRendering()
{
    // if topology of the tetra-mesh has changed
    if (mBoundaryMeshTopologyNeedsUpdate)
    {
        //TODO
    }

    // if geometry of the tetra-mesh has changed
    if (mBoundaryMeshGeometryNeedsUpdate)    
    {
        // walk through all vertices, zero their normals and update its positions
        for (BoundaryVertexContainer::Iterator vertex(mBoundaryVertexContainer); vertex.isValid(); vertex++)
        {
            vertex->position() = mNodeArrayList[static_cast<_Vertex*>((Vertex*)vertex)
									->getReferencedNodeID()].currState().position;
            vertex->beginNormalCalculation();
        }

        // walk through all faces
        for (BoundaryFaceContainer::Iterator face(mBoundaryFaceContainer); face.isValid(); face++)
        {
            // re-compute face normal
            face->updateNormal();

            // and add it to the normals of all its vertices
            face->distributeNormal();
        }

        // walk through all vertices and re-compute their normals    
        for (BoundaryVertexContainer::Iterator vertex(mBoundaryVertexContainer); vertex.isValid(); vertex++)
            vertex->endNormalCalculation();    
    }
}
//-----------------------------------------------------------------------------


//===========================================================================================
// ComModelStructure methods
//===========================================================================================
void dylab::TetraMesh_ModelStructure::translate(const Vector3 & _trans)
{
    for (uint32_t i = 0; i < mNodeArrayList.getElementCount(); i++)
        mNodeArrayList[i].currState().position += _trans;
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::rotate(const Vector3 & _axis, real_t _radians)
{
	// build rotation matrix
	Matrix3x3 rotation;
	rotation.buildFromAxisAndAngle(_axis, _radians);

	// rotate the points
    for (NodeDataContainer::Iterator nodeData(mNodeDataContainer); nodeData.isValid(); nodeData++)
        nodeData->currState().position = rotation * nodeData->currState().position;
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::scale(const Vector3 & _scale)
{
    for (NodeDataContainer::Iterator nodeData(mNodeDataContainer); nodeData.isValid(); nodeData++)
        nodeData->currState().position.mul3(_scale);
}
//-----------------------------------------------------------------------------

//===========================================================================================
// TetraMesh_ModelStructure methods
//===========================================================================================
dylab::TetraMesh_ModelStructure::nodeid_t dylab::TetraMesh_ModelStructure::addNodes(uint32_t nodeCount) 
{
    // create new elements in the node array list
    nodeid_t firstNodeID = mNodeArrayList.addElements(nodeCount);

    // reset their reference count attribute
    for (uint32_t i = 0; i < nodeCount; i++)
    {
        nodeid_t nodeID = getNodeIndexID(i) + firstNodeID;
        getNode(nodeID)->resetRefs();
        getNode(nodeID)->flags().clearAllFlags();
        getNode(nodeID)->setMass(0);
    }

    // return the id of the first created node
    return firstNodeID;
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::removeTetra(tetraid_t tetraID) 
{
    //
    mHFaceArrayList.removeElements(_tetraToHFaceID(tetraID, 0), 4);
            
    // TODO - update all the other structures
    
    // update the opposite half-face list also

    // TODO - call all registered instances of interface ITetraMeshTopologyChangeListeners

}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::getHFaceNodeIDs(hfaceid_t hfaceID, nodeid_t * n1, nodeid_t * n2, nodeid_t * n3) 
{
    hfaceid_t firstHFaceID = _firstHFaceID(hfaceID);
    switch (hfaceID % 4) 
    {
    case 0:
        *n1 = mHFaceArrayList[firstHFaceID + 1];
        *n2 = mHFaceArrayList[firstHFaceID + 2];
        *n3 = mHFaceArrayList[firstHFaceID + 3];
        break;
    case 1:
        *n1 = mHFaceArrayList[firstHFaceID + 2];
        *n2 = mHFaceArrayList[firstHFaceID + 0];
        *n3 = mHFaceArrayList[firstHFaceID + 3];
        break;
    case 2:
        *n1 = mHFaceArrayList[firstHFaceID + 3];
        *n2 = mHFaceArrayList[firstHFaceID + 0];
        *n3 = mHFaceArrayList[firstHFaceID + 1];
        break;
    case 3:
        *n1 = mHFaceArrayList[firstHFaceID + 0];
        *n2 = mHFaceArrayList[firstHFaceID + 2];
        *n3 = mHFaceArrayList[firstHFaceID + 1];
        break;
    }
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::getTetraNodeIDs(tetraid_t tetraID, nodeid_t *n1, nodeid_t *n2, nodeid_t *n3, nodeid_t *n4) 
{
    hfaceid_t hfaceID = _tetraToHFaceID(tetraID, 0);
    *n1 = mHFaceArrayList[hfaceID + 0];
    *n2 = mHFaceArrayList[hfaceID + 1];
    *n3 = mHFaceArrayList[hfaceID + 2];
    *n4 = mHFaceArrayList[hfaceID + 3];
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::_addTri(hfaceid_t _hfaceID, nodeid_t _n1, nodeid_t _n2, nodeid_t _n3)
{
    // compute triangle id 
    Triangle::id_t triID = _hashNodeTriple(_n1, _n2, _n3);

    // try to find the triangle by its id
    _Triangle * tri = static_cast<_Triangle*>(mTriContainer.find(triID));
    if (tri != NULL) 
    {
        //
        hfaceid_t oppHFaceID = tri->getId();

        //
        _checkNodeTripleHash(triID, oppHFaceID, _hfaceID);

        //
        tri->setBoundary(false);

        //
        mOppositeHFaceArrayList[_hfaceID] = oppHFaceID;
        mOppositeHFaceArrayList[oppHFaceID] = _hfaceID;        
    }
    else 
    {
        //
        mOppositeHFaceArrayList[_hfaceID] = OHF_NONE;

        // insert the triangle of the created tetrahedron into the ocntainer
        mTriContainer.insert(triID, new _Triangle(_hfaceID, massPointReader().get(_n1), massPointReader().get(_n2), 
			massPointReader().get(_n3), true));
    }
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::_addEdge(nodeid_t _n1, nodeid_t _n2)
{    
    // compute the edge id
    Edge::id_t edgeID = HashGenerator<Edge::id_t>::SortedAndFactorised<>()
		.addValue(_n1, 157).addValue(_n2, 397).generate().getHash();

    // try to find the edge by its id
    Edge * edge = mEdgeContainer.find(edgeID);
    if (edge == NULL)
        mEdgeContainer.insert(edgeID, new Edge(edgeID, massPointReader().get(_n1), massPointReader().get(_n2)));    
    //else
    //    edge->incRef();
}
//-----------------------------------------------------------------------------
dylab::uint32_t dylab::TetraMesh_ModelStructure::_hashNodeTriple(nodeid_t n1, nodeid_t n2, nodeid_t n3) 
{   
    // generate and return hash value for the sorted node triple
    return HashGenerator<nodeid_t>::SortedAndFactorised<>().addValue(n1, 157).addValue(n2, 397).addValue(n3, 1129).generate().getHash();
}
//-----------------------------------------------------------------------------
void dylab::TetraMesh_ModelStructure::_checkNodeTripleHash(uint32_t hash, hfaceid_t oppHFaceID, hfaceid_t hfaceID)
{
    // retrieve hface node ids
    nodeid_t oh1, oh2, oh3, n1, n2, n3;
    getHFaceNodeIDs(oppHFaceID, &oh1, &oh2, &oh3);
    getHFaceNodeIDs(hfaceID, &n1, &n2, &n3);

    // sort node triples
    nodeid_t tmp;   
    if (oh1 > oh2) { tmp = oh2, oh2 = oh1, oh1 = tmp; }
    if (oh2 > oh3) { tmp = oh3, oh3 = oh2, oh2 = tmp; }
    if (oh1 > oh2) { tmp = oh2, oh2 = oh1, oh1 = tmp; }
    if (n1 > n2) { tmp = n2, n2 = n1, n1 = tmp; }
    if (n2 > n3) { tmp = n3, n3 = n2, n2 = tmp; }
    if (n1 > n2) { tmp = n2, n2 = n1, n1 = tmp; }

    if ((oh1 != n1) || (oh2 != n2) || (oh3 != n3)) 
        DYLAB_LOG("Collision has been found!");
}
//-----------------------------------------------------------------------------